import * as fs from 'fs/promises';
import * as path from 'path';

import { TestFixture, Setup, Expect, AsyncTest, SpyOn, Any, AsyncSetupFixture, Teardown } from 'alsatian';
import type { SuccessObject } from 'jsonrpc-lite';

import { AppStatus } from '../../../src/definition/AppStatus';
import { UserStatusConnection, UserType } from '../../../src/definition/users';
import type { AppManager } from '../../../src/server/AppManager';
import type { IParseAppPackageResult } from '../../../src/server/compiler';
import { AppAccessorManager, AppApiManager } from '../../../src/server/managers';
import { DenoRuntimeSubprocessController } from '../../../src/server/runtime/deno/AppsEngineDenoRuntime';
import type { IAppStorageItem } from '../../../src/server/storage';
import { TestInfastructureSetup } from '../../test-data/utilities';

@TestFixture()
export class DenuRuntimeSubprocessControllerTestFixture {
	private manager: AppManager;

	private controller: DenoRuntimeSubprocessController;

	private appPackage: IParseAppPackageResult;

	private appStorageItem: IAppStorageItem;

	@AsyncSetupFixture
	public async fixture() {
		const infrastructure = new TestInfastructureSetup();
		this.manager = infrastructure.getMockManager();

		const accessors = new AppAccessorManager(this.manager);

		this.manager.getAccessorManager = () => accessors;

		const api = new AppApiManager(this.manager);

		this.manager.getApiManager = () => api;

		const appPackage = await fs.readFile(path.join(__dirname, '../../test-data/apps/hello-world-test_0.0.1.zip'));

		this.appPackage = await this.manager.getParser().unpackageApp(appPackage);

		this.appStorageItem = {
			id: 'hello-world-test',
			status: AppStatus.MANUALLY_ENABLED,
		} as IAppStorageItem;
	}

	@Setup
	public setup() {
		this.controller = new DenoRuntimeSubprocessController(this.manager, this.appPackage, this.appStorageItem);
		this.controller.setupApp();
	}

	@Teardown
	public teardown() {
		this.controller.stopApp();
	}

	@AsyncTest('correctly identifies a call to the HTTP accessor')
	public async testHttpAccessor() {
		const spy = SpyOn(this.manager.getBridges().getHttpBridge(), 'doCall');

		// eslint-disable-next-line
        const r = await this.controller['handleAccessorMessage']({
			type: 'request' as any,
			payload: {
				jsonrpc: '2.0',
				id: 'test',
				method: 'accessor:getHttp:get',
				params: ['https://google.com', { content: "{ test: 'test' }" }],
				serialize: () => '',
			},
		});

		Expect(this.manager.getBridges().getHttpBridge().doCall).toHaveBeenCalledWith(
			Any(Object).thatMatches({
				appId: '9c1d62ca-e40f-456f-8601-17c823a16c68',
				method: 'get',
				url: 'https://google.com',
			}),
		);

		Expect(r.result).toEqual({
			method: 'get',
			url: 'https://google.com',
			content: "{ test: 'test' }",
			statusCode: 200,
			headers: {},
		});

		spy.restore();
	}

	@AsyncTest('correctly identifies a call to the IRead accessor')
	public async testIReadAccessor() {
		const spy = SpyOn(this.manager.getBridges().getUserBridge(), 'doGetByUsername');

		spy.andReturn(
			Promise.resolve({
				id: 'id',
				username: 'rocket.cat',
				isEnabled: true,
				emails: [],
				name: 'name',
				roles: [],
				type: UserType.USER,
				active: true,
				utcOffset: 0,
				status: 'offline',
				statusConnection: UserStatusConnection.OFFLINE,
				lastLoginAt: new Date(),
				createdAt: new Date(),
				updatedAt: new Date(),
			}),
		);

		// eslint-disable-next-line
        const { id, result } = await this.controller['handleAccessorMessage']({
			type: 'request' as any,
			payload: {
				jsonrpc: '2.0',
				id: 'test',
				method: 'accessor:getReader:getUserReader:getByUsername',
				params: ['rocket.cat'],
				serialize: () => '',
			},
		});

		Expect(this.manager.getBridges().getUserBridge().doGetByUsername).toHaveBeenCalledWith(
			'rocket.cat',
			'9c1d62ca-e40f-456f-8601-17c823a16c68',
		);

		Expect(id).toBe('test');
		Expect((result as any).username).toEqual('rocket.cat');
	}

	@AsyncTest('correctly identifies a call to the IEnvironmentReader accessor via IRead')
	public async testIEnvironmentReaderAccessor() {
		// eslint-disable-next-line
        const { id, result } = await this.controller['handleAccessorMessage']({
			type: 'request' as any,
			payload: {
				jsonrpc: '2.0',
				id: 'requestId',
				method: 'accessor:getReader:getEnvironmentReader:getServerSettings:getOneById',
				params: ['setting test id'],
				serialize: () => '',
			},
		});

		Expect(id).toBe('requestId');
		Expect((result as any).id).toEqual('setting test id');
	}

	@AsyncTest('correctly identifies a call to create a visitor via the LivechatCreator')
	public async testLivechatCreator() {
		const spy = SpyOn(this.manager.getBridges().getLivechatBridge(), 'doCreateVisitor');

		spy.andReturn(Promise.resolve('random id'));

		// eslint-disable-next-line
        const { id, result } = await this.controller['handleAccessorMessage']({
			type: 'request' as any,
			payload: {
				jsonrpc: '2.0',
				id: 'requestId',
				method: 'accessor:getModifier:getCreator:getLivechatCreator:createVisitor',
				params: [
					{
						id: 'random id',
						token: 'random token',
						username: 'random username for visitor',
						name: 'Random Visitor',
					},
				],
				serialize: () => '',
			},
		});

		// Making sure `handleAccessorMessage` correctly identified which accessor it should resolve to
		// and that it passed the correct arguments to the bridge method
		Expect(this.manager.getBridges().getLivechatBridge().doCreateVisitor).toHaveBeenCalledWith(
			Any(Object).thatMatches({
				id: 'random id',
				token: 'random token',
				username: 'random username for visitor',
				name: 'Random Visitor',
			}),
			'9c1d62ca-e40f-456f-8601-17c823a16c68',
		);

		Expect(id).toBe('requestId');
		Expect(result).toEqual('random id');

		spy.restore();
	}

	@AsyncTest('correctly identifies a call to the message bridge')
	public async testMessageBridge() {
		const spy = SpyOn(this.manager.getBridges().getMessageBridge(), 'doCreate');

		spy.andReturn(Promise.resolve('random-message-id'));

		const messageParam = {
			room: { id: '123' },
			sender: { id: '456' },
			text: 'Hello World',
			alias: 'alias',
			avatarUrl: 'https://avatars.com/123',
		};

		// eslint-disable-next-line
        const response = await this.controller['handleBridgeMessage']({
			type: 'request' as any,
			payload: {
				jsonrpc: '2.0',
				id: 'requestId',
				method: 'bridges:getMessageBridge:doCreate',
				params: [messageParam, 'APP_ID'],
				serialize: () => '',
			},
		});
		const { id, result } = response as SuccessObject;

		Expect(this.manager.getBridges().getMessageBridge().doCreate).toHaveBeenCalledWith(
			messageParam,
			'9c1d62ca-e40f-456f-8601-17c823a16c68',
		);

		Expect(id).toBe('requestId');
		Expect(result).toEqual('random-message-id');

		spy.restore();
	}
}
